class_name Player
extends CharacterBody2D

# -------------------------变量声明-----------------------------#
enum Direction {
	LEFT = -1,
	RIGHT = +1
}
enum State {
	IDLE,
	RUN,
	JUMP,
	FALL,
	LAND,
	WALL_SLIDING,
	WALL_JUMP,
	SLIDE_START,
	SLIDE_LOOP,
	SLIDE_END,
	ATTACK_1,
	ATTACK_2,
	ATTACK_3,
	HURT,
	DIE,
}

const GROUND_STATE := [State.IDLE, State.RUN, State.LAND]
const ATTACK := 2
# 击退距离
const KNOCKBACK_AMOUNT := 250
const RUN_SPEED := 160
const JUMP_VELOCITY := -340
const SHORT_JUMP := -170
const WALL_JUMP_VELOCITY := Vector2(380, -280)
# 运动加速度 在地面到达运动速度时间为0.2秒
const ACCELERATION := RUN_SPEED / .2
# 运动加速度 在空中到达运动速度的时间0.02秒
const AIR_ACCELERATION := RUN_SPEED / .1
# 滑动持续时间
const SLIDE_DURATION := 0.3
# 滑动速度
const SLIDE_SPEED := 256
# 滑动耗精
const SLIDE_ENERGY := 3.5
# 着陆会产生僵直的高度
const LAND_HEIGHT := 100.0

@export var canCombo := false
@export var direction := Direction.RIGHT:
	set(v):
		direction = v
		if not is_node_ready():
			await ready
		graphics.scale.x = direction

var gravity := ProjectSettings.get("physics/2d/default_gravity") as float
var onFloor := false
var shouldJump
# 玩家动向
var movement
# 连招
var isCombo := false
# 受到伤害
var pending_damage: Damage
# 高度起点
var fallFrom: float
# 互动对象集合
var interactObj: Array[Interact]

# 人物外形
@onready var graphics: Node2D = $Graphics
# 人物动画
@onready var animationPlayer: AnimationPlayer = $AnimationPlayer
# 滞空计时器
@onready var coyoteTimer: Timer = $CoyoteTimer
@onready var handCheck = $Graphics/HandCheck
@onready var footCheck = $Graphics/FootCheck
@onready var stateMachine: Node = $StateMachine
@onready var stats: Node = Game.playerStats
@onready var invincible: Timer = $InvincibleTimer
@onready var interaction: AnimatedSprite2D = $Interaction


# -------------------------生命周期-----------------------------#
# 处理玩家输入---跳跃键松开的时间决定跳跃速度
func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed('attack') and canCombo:
		isCombo = true
	if event.is_action_released('jump') and velocity.y < SHORT_JUMP:
		velocity.y = SHORT_JUMP
	if event.is_action_pressed('interact') and interactObj:
		interactObj.back().interact()

# 初始化
func _ready() -> void:
	stand(0.01)

# 状态流转
func getNextState(state: State) -> int:
	if !stats.health:
		return State.DIE if state != State.DIE else stateMachine.KEEP_CURRENT
	if pending_damage:
		return State.HURT

	# 滞空时延时器时间内（0.1s）可触发跳跃
	var canJump = onFloor or coyoteTimer.time_left > 0
	shouldJump = canJump and Input.is_action_pressed('jump')
	if shouldJump: return State.JUMP
	movement = Input.get_axis("move_left", "move_right")

	if state in GROUND_STATE and not onFloor:
		return State.FALL
	
	var isStill = is_zero_approx(movement) and is_zero_approx(velocity.x)
	
	match state:
		State.IDLE:
			if Input.is_action_just_pressed('attack'):
				return State.ATTACK_1
			if Input.is_action_just_pressed('slide') and stats.energy >= SLIDE_ENERGY:
				return State.SLIDE_START
			if not isStill:
				return State.RUN
		State.RUN:
			if Input.is_action_just_pressed('attack'):
				return State.ATTACK_1
			if Input.is_action_just_pressed('slide') and stats.energy >= SLIDE_ENERGY:
				return State.SLIDE_START
			if isStill:
				return State.IDLE
		State.JUMP:
			if velocity.y >= 0:
				return State.FALL
		State.FALL:
			if onFloor:
				var height := position.y - fallFrom
				return State.LAND if height >= LAND_HEIGHT else State.RUN
			if canWallSlide():
				return State.WALL_SLIDING
			
		State.LAND:
			if not animationPlayer.is_playing():
				return State.IDLE
		State.WALL_SLIDING:
			if Input.is_action_pressed('jump'):
				return State.WALL_JUMP
			if onFloor:
				return State.IDLE
			if not is_on_wall():
				return State.FALL
		State.WALL_JUMP:
			if canWallSlide():
				return State.WALL_SLIDING
			if velocity.y >= 0:
				return State.FALL
		State.ATTACK_1:
			if not animationPlayer.is_playing():
				return State.ATTACK_2 if isCombo else State.IDLE
		State.ATTACK_2:
			if not animationPlayer.is_playing():
				return State.ATTACK_3 if isCombo else State.IDLE
		State.ATTACK_3:
			if not animationPlayer.is_playing():
				return State.IDLE
		State.HURT:
			if not animationPlayer.is_playing():
				return State.RUN
		State.SLIDE_START:
			if not animationPlayer.is_playing():
				return State.SLIDE_LOOP
		State.SLIDE_LOOP:
			if stateMachine.stateTime > SLIDE_DURATION or is_on_wall():
				return State.SLIDE_END
		State.SLIDE_END:
			if not animationPlayer.is_playing():
				return State.IDLE
	return StateMachine.KEEP_CURRENT

# 帧流动
func tickPhysics(state: State, delta: float) -> void:
	# 交互按钮可见
	interaction.visible = !interactObj.is_empty()

	# 无敌帧效果
	if invincible.time_left > 0:
		graphics.modulate.a = sin(Time.get_ticks_msec() / float(20)) * 0.5 + 0.5
	else:
		graphics.modulate.a = 1

	match state:
		State.IDLE:
			move(gravity, delta)
		State.RUN:
			move(gravity, delta)
		State.JUMP:
			move(gravity, delta)
		State.FALL:
			move(gravity, delta)
		State.LAND:
			stand(delta)
		State.WALL_SLIDING:
			move(gravity / 3, delta)
		State.WALL_JUMP:
			if stateMachine.stateTime < 0.1:
				stand(delta)
				direction = Direction.LEFT if get_wall_normal().x < 0 else Direction.RIGHT
			else:
				move(gravity, delta)
		State.ATTACK_1, State.ATTACK_2, State.ATTACK_3, State.HURT, State.DIE:
			stand(delta)
		State.SLIDE_END:
			stand(delta)
		State.SLIDE_START, State.SLIDE_LOOP:
			slide(delta)

# -------------------------函数调用-----------------------------#
# 根据状态 变更动画及后续操作
func transitionState(from: State, to: State) -> void:
	# print('hero:',"[%s] %s => %s" % [
	# 	Engine.get_physics_frames(),
	# 	State.keys()[from] if from!=-1 else 'start',
	# 	State.keys()[to]
	# ])
	if from not in GROUND_STATE and to in GROUND_STATE:
		coyoteTimer.stop()
	match to:
		State.IDLE:
			animationPlayer.play('idle')
		State.RUN:
			animationPlayer.play('run')
		State.JUMP:
			animationPlayer.play('jump')
			velocity.y = JUMP_VELOCITY
			coyoteTimer.stop()
			Sounds.play('Jump')
		State.FALL:
			animationPlayer.play('fall')
			if from in GROUND_STATE:
				coyoteTimer.start()
			fallFrom = position.y
		State.LAND:
			animationPlayer.play('land')
		State.WALL_SLIDING:
			animationPlayer.play('wallSliding')
		State.WALL_JUMP:
			animationPlayer.play('jump')
			velocity = WALL_JUMP_VELOCITY
			velocity.x *= get_wall_normal().x
			
		State.SLIDE_START:
			animationPlayer.play('slideStart')
			stats.energy -= SLIDE_ENERGY
		State.SLIDE_LOOP:
			animationPlayer.play('slideLoop')
		State.SLIDE_END:
			animationPlayer.play('slideEnd')

		State.ATTACK_1:
			animationPlayer.play('attack1')
			isCombo = false
			Sounds.play('Attack1')
		State.ATTACK_2:
			animationPlayer.play('attack2')
			isCombo = false
			Sounds.play('Attack2')
		State.ATTACK_3:
			animationPlayer.play('attack3')
			isCombo = false
			Sounds.play('Attack3')
		State.HURT:
			animationPlayer.play('hurt')
		State.DIE:
			die()
	pass

# 移动
func move(g, delta: float) -> void:
	onFloor = is_on_floor()
	var acceleration := ACCELERATION if onFloor else AIR_ACCELERATION
	velocity.x = move_toward(velocity.x, movement * RUN_SPEED, acceleration * delta)
	velocity.y += g * delta
	# 有输入时 水平翻转
	if not is_zero_approx(movement):
		direction = Direction.LEFT if movement < 0 else Direction.RIGHT
	move_and_slide()

# 人物静止，不接受输入
func stand(delta: float) -> void:
	onFloor = is_on_floor()
	var acceleration := ACCELERATION if onFloor else AIR_ACCELERATION
	velocity.x = move_toward(velocity.x, 0.0, acceleration * delta)
	velocity.y += gravity * delta
	move_and_slide()

# 滑墙
func canWallSlide() -> bool:
	return is_on_wall() and handCheck.is_colliding() and footCheck.is_colliding()

# 地面滚动
func slide(delta: float) -> void:
	velocity.x = graphics.scale.x * SLIDE_SPEED
	velocity.y += gravity * delta
	move_and_slide()
	pass

# 获取交互区域
func addInteract(item: Interact) -> void:
	if item in interactObj or stateMachine.currentState == State.DIE:
		return
	interactObj.append(item)

# 去除交互区域
func removeInteract(item: Interact) -> void:
	interactObj.erase(item)

# 计算攻击力伤害
func damage():
	return ATTACK

# 受到伤害
func getHurt():
	Game.shakeCamera(4)

	stats.health -= pending_damage.amount
	var dir := pending_damage.source.global_position.direction_to(global_position)
	velocity = dir * KNOCKBACK_AMOUNT
	
	pending_damage = null
	invincible.start()

# 死亡事件
func die() -> void:
	invincible.stop()
	interactObj.clear()
	animationPlayer.play('die')
	await animationPlayer.animation_finished
	Game.deadScreen.show()


# 受伤信号
func _on_hurt_box_hurt(hitBox: HitBox) -> void:
	if invincible.time_left > 0: return

	pending_damage = Damage.new()
	pending_damage.amount = hitBox.owner.damage()
	# print('attack',pending_damage.amount)
	pending_damage.source = hitBox.owner
	getHurt()

# 攻击信号
func _on_hit_box_hit(_hurtBox: HurtBox) -> void:
	Game.shakeCamera(2)
	# 攻击慢
	Engine.time_scale = 0.01
	await get_tree().create_timer(0.1, true, false, true).timeout
	Engine.time_scale = 1
